package com.guavamail.smtp;

import java.io.IOException;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;

import org.apache.mina.core.buffer.BufferDataException;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.AttributeKey;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;

public class SMTPDecoder implements ProtocolDecoder {
	private final AttributeKey CONTEXT = new AttributeKey(getClass(), "context");

	protected final static String TMPFILE_PREFIX = "subetha";
	protected final static String TMPFILE_SUFFIX = ".eml";

	private final Charset charset;

	private final static byte[] SMTP_CMD_DELIMITER = new byte[] { '\r', '\n' };
	private final static byte[] SMTP_DATA_DELIMITER = new byte[] { '\r', '\n', '.', '\r', '\n' };

	/** When to trigger */
	private int threshold;

	/**
	 * <a href="http://www.ietf.org/rfc/rfc2822.txt#s2.1.1.">RFC 2822</a>
	 */
	private int maxLineLength = 998;

	public SMTPDecoder(Charset charset, int thresholdBytes) {
		if (charset == null) {
			throw new IllegalArgumentException("charset parameter shuld not be null");
		}

		this.charset = charset;

		this.threshold = thresholdBytes;
	}

	@Override
	public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
		SMTPDecoderContext ctx = getContext(session);
		int matchCount = ctx.getMatchCount();

		SMTPContext minaCtx = (SMTPContext) session.getAttribute(SMTPConnectionHandler.CONTEXT_ATTRIBUTE);

		boolean dataMode = minaCtx.getSessionStatus().isDataMode();
		ctx.setDataMode(dataMode);
		byte[] delimBuf = dataMode ? SMTP_DATA_DELIMITER : SMTP_CMD_DELIMITER;

		// Try to find a match
		int oldPos = in.position();
		int oldLimit = in.limit();

		if (matchCount == delimBuf.length)
			matchCount = 0;

		while (in.remaining() > 0) {
			byte b = in.get();
			if (delimBuf[matchCount] == b) {
				matchCount++;
				if (matchCount == delimBuf.length) {
					// Found a match.
					int pos = in.position();
					in.limit(pos);
					in.position(oldPos);

					ctx.write(in);

					in.limit(oldLimit);
					in.position(pos);

					if (ctx.getOverflowPosition() == 0) {
						IoBuffer buf = ctx.getBuffer();
						buf.flip();

						try {
							if (dataMode) {
								delimBuf = SMTP_CMD_DELIMITER;
								out.write(ctx.getInputStream());
							} else {
								buf.limit(buf.limit() - matchCount);
								out.write(buf.getString(ctx.getDecoder()));
							}
						} catch (IOException ioex) {
							throw new CharacterCodingException();
						} finally {
							ctx.reset();
							buf.clear();
						}
					} else {
						String msg = "Line is too long: " + ctx.getOverflowPosition();
						ctx.reset();
						throw new BufferDataException(msg);
					}

					oldPos = pos;
					matchCount = 0;
				}
			} else {
				// fix for DIRMINA-506
				in.position(Math.max(0, in.position() - matchCount));
				matchCount = 0;
			}
		}

		// Put remainder to buf.
		in.position(oldPos);
		ctx.write(in);

		ctx.setMatchCount(matchCount);
	}

	/**
	 * Return the context for this session
	 */
	private SMTPDecoderContext getContext(IoSession session) {
		SMTPDecoderContext ctx = (SMTPDecoderContext) session.getAttribute(CONTEXT);
		if (ctx == null) {
			ctx = new SMTPDecoderContext(this);
			session.setAttribute(CONTEXT, ctx);
		}
		return ctx;
	}

	@Override
	public void dispose(IoSession session) throws Exception {
		SMTPDecoderContext ctx = (SMTPDecoderContext) session.getAttribute(CONTEXT);
		if (ctx != null) {
			ctx.getBuffer().free();
			ctx.closeOutputStream();
			session.removeAttribute(CONTEXT);
		}
	}

	@Override
	public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception {

	}

	public Charset getCharset() {
		return charset;
	}

	public int getMaxLineLength() {
		return maxLineLength;
	}

	public void setMaxLineLength(int maxLineLength) {
		if (maxLineLength <= 0) {
			throw new IllegalArgumentException("maxLineLength: " + maxLineLength);
		}

		this.maxLineLength = maxLineLength;
	}

	public int getThreshold() {
		return threshold;
	}

	public void setThreshold(int threshold) {
		this.threshold = threshold;
	}

}
